1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
import { GetServerSideProps, NextPage } from 'next';
import React, { useEffect } from 'react';
import dynamic from 'next/dynamic';
import cookie from 'cookie';
import { getProductById } from '~/services/product';
import { createSlug, getIdFromSlug } from '~/libs/slug'; // <-- tambahin createSlug
import { IProductDetail } from '~/types/product';
import { Seo } from '~/components/seo';
import { useRouter } from 'next/router';
import { useProductContext } from '@/contexts/ProductContext';
const BasicLayout = dynamic(
() => import('@/core/components/layouts/BasicLayout'),
{ ssr: false }
);
const ProductDetail = dynamic(() => import('~/modules/product-detail'), {
ssr: false,
});
type PageProps = {
product: IProductDetail;
canonicalPath: string;
};
export const getServerSideProps: GetServerSideProps<PageProps> = async (
context
) => {
const { slug } = context.query;
// ambil cookie pricelist tier
const cookieString = context.req.headers.cookie;
const cookies = cookieString ? cookie.parse(cookieString) : {};
const auth = cookies?.auth ? JSON.parse(cookies.auth) : {};
const tier = auth?.pricelist || '';
// ambil ID produk dari slug URL
const productId = getIdFromSlug(slug as string);
// fetch data produk dari backend lo
const product = await getProductById(productId, tier);
// hard guard: produk gak ada -> 404
if (!product) return { notFound: true };
// guard: gak ada varian harga valid -> 404
const hasValidVariant =
Array.isArray(product.variants) &&
product.variants.some((v) => (v?.price?.price ?? 0) > 0);
// if (!hasValidVariant) return { notFound: true };
// bikin canonical path yang BERSIH dan KONSISTEN dari data produk,
// bukan dari URL request user (jadi gak ikut ?utm_source, ?ref=, dsb)
const canonicalPath = createSlug(
'/shop/product/', // ganti ini sesuai prefix route produk lo yang SEBENARNYA
product?.name || '',
product?.id,
false // false = jangan include host di sini
);
return {
props: {
product,
canonicalPath,
},
};
};
const ProductDetailPage: NextPage<PageProps> = ({ product, canonicalPath }) => {
const router = useRouter();
const { setProduct } = useProductContext();
// taruh product di context global lo
useEffect(() => {
if (product) setProduct(product);
}, [product, setProduct]);
// rapihin origin biar gak double slash
const origin = (process.env.NEXT_PUBLIC_SELF_HOST || '').replace(/\/+$/, '');
const pathClean = canonicalPath.startsWith('/')
? canonicalPath
: `/${canonicalPath}`;
const url = origin + pathClean;
// optional: pastiin OG image absolute URL
const ogImageUrl = product?.image?.startsWith('http')
? product.image
: origin + product?.image;
return (
<BasicLayout>
<Seo
title={`${product.name} - Indoteknik.com`}
description='Temukan pilihan produk B2B Industri & Alat Teknik untuk Perusahaan, UMKM & Pemerintah dengan lengkap, mudah dan transparan.'
canonical={url} // <- ini diprioritaskan sama komponen Seo
openGraph={{
url,
images: [
{
url: ogImageUrl,
width: 800,
height: 800,
alt: product?.name,
},
],
type: 'product',
}}
additionalMetaTags={[
{
name: 'keywords',
content: `${product?.name}, Harga ${product?.name}, Beli ${product?.name}, Spesifikasi ${product?.name}`,
},
]}
/>
<div className='md:container pt-4 md:pt-6'>
<ProductDetail product={product} />
</div>
</BasicLayout>
);
};
export default ProductDetailPage;
|